 /*
  *   *       Please Visit us at www.codemiles.com     *
  *  This Program was Developed by www.codemiles.com forums Team
  *  *           Please Don't Remove This Comment       *
  */
package serverchat;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.*;
import java.util.*;

import javax.swing.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import org.w3c.dom.*;

/**
 *
 * @author Muhamed
 */

/**
 * 
 * Tipos de mensajes:
 * 0: Server
 * 1: Broadcast
 * 2: Unicast
 * 3: Login
 * 4: Logout
 * 5: Welcome
 */

public class MyFrame extends JFrame {
    
    /** Creates a new instance of myFrame */
    private JTextArea chatBox=new JTextArea(10,45);
    private JScrollPane myChatHistory=new JScrollPane(chatBox,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
    private JTextArea userText = new JTextArea(5,40);
    private JScrollPane myUserHistory=new JScrollPane(userText,JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
            JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    private JButton send = new JButton("Send");
    private JButton start = new JButton("Start Server!");
    private Server chatServer;
    private InetAddress serverAddress;
    private Document document;
    private Element eRoot, eType, eUser, eMessage, eUsersList, eReceiver;
    private String sType, sUser, sMessage, sReceiver;
    private DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance ( );
    private DocumentBuilder builder;
    private TransformerFactory tFactory;
    private Transformer transformer;
    private DOMSource source;
    private StringWriter sw;
    private StreamResult result;
    
    
    public MyFrame() {
        setTitle("Server");
        setSize(560,400);
        Container cp=getContentPane();
        cp.setLayout(new FlowLayout());
        cp.add(new JLabel("Server History"));
        cp.add(myChatHistory);
        cp.add(new JLabel("Chat Box : "));
        cp.add(myUserHistory);
        cp.add(send);
        cp.add(start);
        
        
        
        start.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                
                chatServer = new Server();
                chatServer.start();
                
            }
        });
        send.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                chatServer.sendServerMessage("0", serverAddress.getHostName() + " < Server > " + userText.getText());
                chatBox.append(serverAddress.getHostName() + " < Server > " + userText.getText() + "\n");
            }
        });
        
        
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
        
        
    }
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        new MyFrame();
    }
    
    
    public class Server extends Thread {
        private static final int PORT=9999;
        private Hashtable<String, ClientSC> clients;
        private ByteBuffer readBuffer;
        private ByteBuffer writeBuffer;
        public  ServerSocketChannel sSChan;
        private Selector readerSelector;
        private CharsetDecoder utf8Decoder;
        
        public Server() {
            clients = new Hashtable<String, ClientSC>();
            readBuffer = ByteBuffer.allocateDirect(300);
            writeBuffer = ByteBuffer.allocateDirect(300);
            utf8Decoder = Charset.forName( "UTF-8").newDecoder();
        }
        
        public void initServer() {
            try {
                sSChan=ServerSocketChannel.open();
                sSChan.configureBlocking(false);
                serverAddress=InetAddress.getLocalHost();
                System.out.println("Server started at: " + serverAddress.toString());
                
                sSChan.socket().bind(new InetSocketAddress(serverAddress,PORT));
                
                readerSelector=Selector.open();
                chatBox.setText(serverAddress.getHostName()+"<Server> Started. \n");
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        
        public void run() {
            initServer();
            
            while(true) {
                acceptNewConnection();
                
                readMessage();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
                
                
            }
        }
        
        public void acceptNewConnection() {
            SocketChannel sC;
            try {
                
                while ((sC = sSChan.accept()) != null) {
                    chatServer.addClient(sC);
                }
                
            } catch (IOException e) {
                e.printStackTrace();
            }
            
        }
        
        public void addClient(SocketChannel sC) {
            try {
                sC.configureBlocking(false);
                SelectionKey key = sC.register(readerSelector, SelectionKey.OP_READ, new StringBuffer());
                //Creamos un nuevo objeto ClientSC y lo adjuntamos al canal del socket de cada cliente
                //Para guardar atributos del cliente en este objeto
                ClientSC clientSC = new ClientSC(sC);
                key.attach(clientSC);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        
        public String createXMLMessage(String sender, String receiver, String type, String message) {
        	
        	String xml = "";
        	
        	try {
				builder = factory.newDocumentBuilder();

        	//Aqui creamos el documento xml y le agregamos los elementos
        	document = builder.newDocument();
        	//Elemento raíz
        	eRoot = document.createElement("root");
        	document.appendChild(eRoot);
        	//Elemento para el tipo de mensaje
            eType = document.createElement("type");
            //El 1 significa mensaje broadcast (default)
            eType.setTextContent(type);
            eRoot.appendChild(eType);
            //Si el mensaje es Login o Logout, enviamos el nickname
            if(type.equals("3") || type.equals("4")) {
            	//Elemento para el nickname
            	eUser = document.createElement("user");
                eUser.setTextContent(sender);
                eRoot.appendChild(eUser);
            }
            //Si el mensaje es de bienvenida, enviamos la lista de usuarios
            else if(type.equals("5")) {
            	//Elemento para la lista de usuarios
            	eUsersList = document.createElement("users-list");
            	for(String key : clients.keySet()) {
            		if(!key.equals(receiver)) {
            	        eUser = document.createElement("user");
                        eUser.setAttribute("name", key);
                        eUsersList.appendChild(eUser);
            		}
            	}
            	eRoot.appendChild(eUsersList);
            }
            eMessage = document.createElement("message");
            eMessage.setTextContent(message);
            //Elemento para el mensaje
            eMessage = document.createElement("message");
            eMessage.setTextContent(message);
            eRoot.appendChild(eMessage);
            document.getDocumentElement().normalize();
            tFactory = TransformerFactory.newInstance();

				transformer = tFactory.newTransformer();

            //Preparamos el documento XML para ser analizado con dom y convertirlo a String
            source = new DOMSource(document);
            sw = new StringWriter();
            result = new StreamResult(sw);
				transformer.transform(source, result);
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
        	
            return sw.toString();
        }
        
        public void sendServerMessage(String type, String messg) {
            if(clients.size() > 0) {
                for(String key : clients.keySet()) {
                    ClientSC clientSC = clients.get(key);
                    sendUnicastMessage(null, clientSC.getName(), type, messg);
                }
            }
        }
        
        public void sendUnicastMessage(String sender, String receiver, String type, String messg) {
            prepareBuffer(createXMLMessage(sender, receiver, type, messg));
            channelWrite(clients.get(receiver).getSC());
        }
        
        public void sendBroadcastMessage(String sender, String type, String messg) {
            prepareBuffer(createXMLMessage(sender, null, type, messg));
            for(String receiver : clients.keySet()) {
                if(!sender.equals(receiver)) {
                    channelWrite(clients.get(receiver).getSC());
                }
            }
        }
        
        public void prepareBuffer(String xml) {
            writeBuffer.clear();
            writeBuffer.put(xml.getBytes());
            writeBuffer.flip();
        }
        
        public void channelWrite(SocketChannel sC) {
            long num = 0;
            long len = writeBuffer.remaining();
            while(num != len) {
                try {
                    num += sC.write(writeBuffer);
                    
                    Thread.sleep(5);
                } catch(Exception ex) {
                    
                }
            }
            writeBuffer.rewind();
        }
        
        public void readMessage() {
            try {               
                readerSelector.selectNow();
                Set readkeys=readerSelector.selectedKeys();
                Iterator iter=readkeys.iterator();
                while(iter.hasNext()) {
                    SelectionKey key = (SelectionKey) iter.next();
                    iter.remove();
                    ClientSC clientSC = (ClientSC) key.attachment();
                    SocketChannel sC = clientSC.getSC();
                    readBuffer.clear();
                    //Leemos el contenido del mensaje y lo almacenamos en un búfer de bytes
                    int num = sC.read(readBuffer);
                    
                    //Si el mensaje del cliente equivale a -1 significa que se perdió la conexión
                    //Y por lo tanto lo removemos de la lista de clientes, mandando un mensaje a los demas
                    if(num == -1) {
                    	//Nos aseguramos de que el nombre del usuario no esté vacío
                    	sUser =  clientSC.getName();
                    	sUser = sUser.length() > 0? sUser: sC.socket().getInetAddress().toString();
                        chatBox.append("The user " + sUser + " has left the chat\n"); 
                        System.out.println("The user " + sUser + " has left the chat");
                        sendBroadcastMessage(clientSC.getName(), "4", "The user " + sUser + " has left the chat");
                        sC.close();
                        clients.remove(clientSC);
                        
                    } else {
                        readBuffer.flip();
                        eRoot = (Element) newDocument().getDocumentElement();
                        eType = (Element) eRoot.getElementsByTagName("type").item(0);
                        sType = eType.getTextContent();
				        //Quiere decir que el usuario acaba de iniciar sesión
                        if(sType.equals("3")) {
                        	eUser = (Element) eRoot.getElementsByTagName("user").item(0);
    				        sUser = eUser.getTextContent();
    				        //Si no proporcionó nickname entonces usamos su direccion IP
    				        sUser = sUser.length() > 0? sUser: sC.socket().getInetAddress().getHostAddress();
    				        clientSC.setName(sUser);
    				        clients.put(sUser, clientSC);
                        	chatBox.append("The user " + sUser + " has joined to the chat\n");
                        	System.out.println("The user " + sUser + " has joined to the chat");
                        	sendUnicastMessage(null, sUser, "5", serverAddress.getHostName()+"<server> Welcome you !\n Note :To exit" +
                                    " from server write 'quit'.");
                            sendBroadcastMessage(sUser, "3", "The user " + sUser + " has joined to the chat\n");
                        }
                        //Quiere decir que el mensaje es broadcast
                        else if(sType.equals("1")) {
                        	sUser = clientSC.getName();
                        	eMessage = (Element) eRoot.getElementsByTagName("message").item(0);
    				        sMessage = eMessage.getTextContent();
                        	//Quiere decir que el usuario quiere abandonar el chat
                        	if(sMessage.equals("quit")) {
                                chatBox.append("Logout: " + sUser + "\n"); 
                                System.out.println("Logout: " + sUser);
                                sendBroadcastMessage(sUser, "4", "Logout: " + sUser + "\n");
                                sC.close();
                                clients.remove(sC);
                            }
                        	//Si no, transmitimos el mensaje 
                        	else {
                        		
                        		if ((sMessage.indexOf("\n") != -1) || (sMessage.indexOf("\r") != -1)) {
                                    sMessage = sMessage.trim();
                                }
                        		
                                chatBox.append(sUser + "> " + sMessage + "\n");
                                sendBroadcastMessage(sUser, "1", sUser + "> " + sMessage + "\n");
                            }
                        	
                        }
                      //Quiere decir que el mensaje es unicast
                        else if(sType.equals("2")) {
                        	sUser = clientSC.getName();
                        	eReceiver = (Element) eRoot.getElementsByTagName("receiver").item(0);
    				        sReceiver = eReceiver.getTextContent();
                        	eMessage = (Element) eRoot.getElementsByTagName("message").item(0);
    				        sMessage = eMessage.getTextContent();
                        		
                            if ((sMessage.indexOf("\n") != -1) || (sMessage.indexOf("\r") != -1)) {
                                sMessage = sMessage.trim();
                            }
                        		
                            chatBox.append(sUser + "> " + sReceiver + "> " + sMessage + "\n");
                            sendUnicastMessage(sUser, sReceiver, "2", sUser + "> " + sMessage + "\n");
                        }

                    }
                }
                
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        
        public Document newDocument(){
            try{
            factory = DocumentBuilderFactory.newInstance();
            String xml = utf8Decoder.decode(readBuffer).toString();
                document = (Document) factory.newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes()));

            } catch (Exception e) {
                e.printStackTrace();
            }
            readBuffer.clear();
            return document;
        }
    }
    
    //Esta clase es para crear los clientes que agregaremos a nuestra lista de clientes
    public class ClientSC {
    	
    	private SocketChannel _sC;
    	private String _name;
    	
    	public ClientSC(SocketChannel sC) {
    		setSC(sC);
    	}

		public SocketChannel getSC() {
			return _sC;
		}

		public void setSC(SocketChannel sC) {
			_sC = sC;
		}

		public String getName() {
			return _name;
		}

		public void setName(String name) {
			_name = name;
		}
    	
    }
    
}
 /*
  *   *       Please Visit us at www.codemiles.com     *
  *  This Program was Developed by www.codemiles.com forums Team
  *  *           Please Don't Remove This Comment       *
  */